package com.eptok.gw.szcups.hsm;

import com.eptok.gw.szcups.util.TripleDES;
import com.solab.iso8583.common.ByteUtil;

/**
 * 
 * 科友(SJL06E)加密机
 * 
 *@author:heqishan, Email:heqishan@gmail.com
 *2013-3-7 下午02:41:40
 */
public class HsmService {
	
	private static final String HSM_MESSAGE_HEADER = "12345678";		//消息头，加密机原样返回由于是短连接，所以随便写，8位长度
	
	private static final int MAC_BLOCK_MAX_LEN = 960;					//最大的算MAC的长度，如果数据长度超过了该值，将分几步来算MAC
	
	private static final String HSM_SPLIT_STR = ";";		//分隔符
	public static final String HSM_DESKEY_MODE = "Z";		//单倍长密钥模式
	public static final String HSM_3DESKEY_MODE = "X";		//单倍长密钥的长度
//	private static final String HSM_KEY_CHECKTYPE_0 = "0";	//加密密钥时的Check的长度，0 16位，1 6位
	private static final String HSM_KEY_CHECKTYPE_1 = "1";	//加密密钥时的Check的长度，0 16位，1 6位
	private static final String HSM_MAX_PIN_LEN = "12";	//最大PIN长度
	private static final String HSM_MAX_SOURCE_PIN_FORMAT = "01";	//源pin格式
	private static final String HSM_MAX_TARGET_PIN_FORMAT = "01";	//目的pin格式
	
	private static final String HSM_COMMON_ZMK_ZPK2LMK_REQ = "FA";		//将由ZMK加密的ZPK转换为由LMK加密(请求)
	private static final String HSM_COMMON_ZMK_ZPK2LMK_RSP = "FB";		//将由ZMK加密的ZPK转换为由LMK加密(响应)
	
	private static final String HSM_COMMON_ZMK_ZAK2LMK_REQ = "FK1";		//将由ZMK加密的ZAK转换为由LMK加密(请求)
	private static final String HSM_COMMON_ZMK_ZAK2LMK_RSP = "FL";		//将由ZMK加密的ZAK转换为由LMK加密(响应)
	
	private static final String HSM_COMMON_PIN_ZPK2ZPK_REQ = "CA";		//将PIN从一个TPK翻译到另一个ZPK(请求)
	private static final String HSM_COMMON_PIN_ZPK2ZPK_RSP = "CB";		//将PIN从一个TPK翻译到另一个ZPK(响应)
	
	private static final String HSM_COMMON_MAC_TAK_REQ = "MU";		//用TAK（LMK）生成一个MAC(请求)
	private static final String HSM_COMMON_MAC_TAK_RSP = "MV";		//用TAK（LMK）生成一个MAC(响应)
	
	private static final String HSM_RSP_SUCCESS_CODE = "00";			//加密机返回的成功响应码
	
	/**
	 * 将由ZMK加密的ZPK转换为由LMK加密
	 * @param zmk
	 * @param sourceZPK
	 * @return zpk
	 * @throws Exception
	 */
	public String zpk_zmk2lmk(String zmk,String sourceZPK,String desMode,String checkcode) throws Exception{
		StringBuilder sBuilder = new StringBuilder(HSM_MESSAGE_HEADER);
		sBuilder.append(HSM_COMMON_ZMK_ZPK2LMK_REQ);
		sBuilder.append(zmk);
		//3des需要加X，des需要加Z
		if(HSM_3DESKEY_MODE.equals(desMode)){
			sBuilder.append(desMode);
		}
		sourceZPK = correctOdd(sourceZPK);	//TODO	修正奇校验码，联网调试/生产的时候删除
		
		sBuilder.append(sourceZPK);
		String rsp =  new HsmSocket().getHsmData(sBuilder.toString());
		if(rsp == null){
			throw new Exception("加密机通讯失败!");
		}
		
		String rspCode = getResponseCode(rsp);
		if(HSM_COMMON_ZMK_ZPK2LMK_RSP.equals(getCommonCode(rsp)) 
				&& HSM_RSP_SUCCESS_CODE.equals(rspCode)){
			String message = rsp.substring(13,45);
			String code = rsp.substring(45,51);
			
			if(checkcode != null && !checkcode.equals("")){
				if(!code.equals(checkcode)){
					throw new Exception("校验码匹配失败！");
				}
			}
			
			return message;
		}else{
			throw new Exception("加密机业务处理失败，失败码为：" + rspCode);
		}
	}
	
	/**
	 * 将ZAK从ZMK转为LMK加密
	 * @param zmk
	 * @param sourceZAK
	 * @param checkcode		为空则不校验校验码
	 * @return
	 * @throws Exception
	 */
	public String zak_zmk2lmk(String zmk,String sourceZAK,String checkcode) throws Exception{
		StringBuilder sBuilder = new StringBuilder(HSM_MESSAGE_HEADER);
		sBuilder.append(HSM_COMMON_ZMK_ZAK2LMK_REQ);
		sBuilder.append(zmk);
		sourceZAK = correctOdd(sourceZAK);	//TODO	修正奇校验码，联网调试/生产的时候删除
		sBuilder.append(sourceZAK);
		sBuilder.append(HSM_SPLIT_STR);
		sBuilder.append(HSM_DESKEY_MODE);
		sBuilder.append(HSM_DESKEY_MODE);
		sBuilder.append(HSM_KEY_CHECKTYPE_1);
		String rsp =  new HsmSocket().getHsmData(sBuilder.toString());
		if(rsp == null){
			throw new Exception("加密机通讯失败!");
		}
		
		String rspCode = getResponseCode(rsp);
		if(HSM_COMMON_ZMK_ZAK2LMK_RSP.equals(getCommonCode(rsp)) 
				&& HSM_RSP_SUCCESS_CODE.equals(rspCode)){
			String message = rsp.substring(12,28);
			String code = rsp.substring(28,34);
			
			if(checkcode != null && !checkcode.equals("")){
				if(!code.equals(checkcode)){
					throw new Exception("校验码匹配失败！");
				}
			}
			
			return message;
		}else{
			throw new Exception("加密机业务处理失败，失败码为：" + rspCode);
		}
	}
	
	/**
	 * 将PIN从一个ZPK翻译到另一个ZPK
	 * @param sourceZPK
	 * @param targetZPK
	 * @param pin
	 * @param cardNum
	 * @return
	 * @throws Exception
	 */
	public String pin_tpk2zpk(String sourceTPK,String targetZPK,String pin,String cardNum)throws Exception{
		StringBuilder sBuilder = new StringBuilder(HSM_MESSAGE_HEADER);
		sBuilder.append(HSM_COMMON_PIN_ZPK2ZPK_REQ);
		sBuilder.append(HSM_3DESKEY_MODE);
		sBuilder.append(sourceTPK);
		sBuilder.append(HSM_3DESKEY_MODE);
		sBuilder.append(targetZPK);
		sBuilder.append(HSM_MAX_PIN_LEN);
		sBuilder.append(pin);
		sBuilder.append(HSM_MAX_SOURCE_PIN_FORMAT);
		sBuilder.append(HSM_MAX_TARGET_PIN_FORMAT);
		sBuilder.append(formatCardNum(cardNum));
		
		String rsp =  new HsmSocket().getHsmData(sBuilder.toString());
		if(rsp == null){
			throw new Exception("加密机通讯失败!");
		}
		
		String rspCode = getResponseCode(rsp);
		if(HSM_COMMON_PIN_ZPK2ZPK_RSP.equals(getCommonCode(rsp)) 
				&& HSM_RSP_SUCCESS_CODE.equals(rspCode)){
			return rsp.substring(14,30);
		}else{
			throw new Exception("加密机业务处理失败，失败码为：" + rspCode);
		}
	}
	
	/**
	 * 生成mac
	 * @param zak
	 * @param macBlock
	 * @return
	 * @throws Exception
	 */
	public String getMac(String zak,byte[] macBlock)throws Exception{
		String iv = "0000000000000000";
		String keyType = "1";	//密钥类型，0 TAK，1 ZAK
		String keyLen;			//密钥长度，0 单倍DES密钥长，1 双倍DES密钥长
		if(zak.length() == 16){
			keyLen = "0";
		}else{
			keyLen = "1";
		}
		String MessageType = "1";	//数据类型，0 二进制数据，1 十六进制数据
		
		int count = 0;
		int macBlockLen = macBlock.length;
		while(count <= (macBlockLen-1)/MAC_BLOCK_MAX_LEN){
			String messageBlockNum;
			int sendBlockLen;
			if(count < (macBlockLen-1)/MAC_BLOCK_MAX_LEN){
				messageBlockNum = "2";
				sendBlockLen = MAC_BLOCK_MAX_LEN;
			}else{
				messageBlockNum = "3";
				sendBlockLen = macBlockLen % MAC_BLOCK_MAX_LEN;
			}
			
			StringBuilder sBuilder = new StringBuilder(HSM_MESSAGE_HEADER);
			sBuilder.append(HSM_COMMON_MAC_TAK_REQ);
			sBuilder.append(messageBlockNum);
			sBuilder.append(keyType);
			sBuilder.append(keyLen);
			sBuilder.append(MessageType);
			sBuilder.append(zak);
			sBuilder.append(iv);
			sBuilder.append(String.format("%04X", sendBlockLen));
			byte[] temp = new byte[sendBlockLen];
			System.arraycopy(macBlock, MAC_BLOCK_MAX_LEN * count, temp, 0, sendBlockLen);
			sBuilder.append(ByteUtil.BinaryToHexString(temp));
			count++;
			
			String rsp =  new HsmSocket().getHsmData(sBuilder.toString());
			if(rsp == null){
				throw new Exception("加密机通讯失败!");
			}
			
			String rspCode = getResponseCode(rsp);
			if(HSM_COMMON_MAC_TAK_RSP.equals(getCommonCode(rsp)) 
					&& HSM_RSP_SUCCESS_CODE.equals(rspCode)){
				iv = rsp.substring(12,28);
			}else{
				throw new Exception("加密机业务处理失败，失败码为：" + rspCode);
			}
		}
		
		return iv;
	}
	
	/**
	 * 将卡号转化为机密机所需格式（账号中去除校验位的最右12位）
	 * @param cardNum
	 * @return
	 */
	public String formatCardNum(String cardNum){
		return cardNum.substring(cardNum.length()-13,cardNum.length()-1);
	}
	
	/**
	 * 返回响应代码
	 * @param rsp
	 * @return
	 */
	private String getCommonCode(String rsp){
		return rsp.substring(8,10);
	}
	
	/**
	 * 返回错误码
	 * @param rsp
	 * @return
	 */
	private String getResponseCode(String rsp){
		return rsp.substring(10,12);
	}
	
	/**
	 * 修正奇校验位
	 * @param src
	 * @return
	 * @throws Exception 
	 */
	private static String correctOdd(String src) throws Exception{
		byte[] key = ByteUtil.hexStringToBytes("1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF");			//3DES明文
		byte[] str_true = TripleDES.decrypt(ByteUtil.hexStringToBytes(src),key);
		correctOdd(str_true);
		byte[] str_result = TripleDES.encrypt(str_true, key);
		return ByteUtil.BinaryToHexString(str_result);
	}
	
	public static void main(String[] args) throws Exception {
		System.out.println(correctOdd("E06E5726E10FAC4D"));
	}
	
	/**
	 * 奇校验位修正
	 * @param source
	 * @return
	 */
	public static byte[] correctOdd(byte[] source){
		short compare = 128;
		int compare_temp;
		int convert_temp;
		for(int i=0; i<source.length; i++){
			int n = 0;
			compare_temp = new Byte(source[i]).intValue();
			convert_temp = compare_temp;
			for(int j=0; j<7; j++){
				if((compare_temp & compare) > 0){
					n++;
				}
				compare_temp = compare_temp<<1;
			}
			if((n % 2) > 0){
				convert_temp = convert_temp & 254;	//奇数，最后一位改为0
			}else{
				convert_temp = convert_temp | 1;	//偶数，最后一位改为1
			}
			source[i] = new Integer(convert_temp).byteValue();
		}
		return source;
	}

}
